/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 *
 */

import crypto from 'crypto';
import os from 'os';
import path from 'path';

import fsExtra from 'fs-extra';

import { buildOptionPath } from '../common/build-option-path-info.js';
import { DefaultTargetConst } from '../const/common-const.js';
import { CodeType } from '../enum/code-type-enum.js';
import { GenerateCodemodel } from '../model/cxx/generate-codemodel.js';
import { GenerateNativeLibrary } from '../model/cxx/generate-native-library.js';
import { CacheEntry, CmakeCache } from '../model/cxx/native-cmake-cache.js';
import { NativeCMakeFiles } from '../model/cxx/native-cmake-files.js';
import { NativeCodemodel } from '../model/cxx/native-codemodel.js';
import { NativeLibraryModel } from '../model/cxx/native-library-model.js';
import { CoreModuleModelImpl } from '../model/module/core-module-model-impl.js';
import { ModuleModel } from '../model/module/module-model.js';
import { ExternalNativeOpt } from '../options/build/build-opt.js';
import { ModuleTargetData } from '../tasks/data/hap-task-target-data.js';
import { PrepareSharedHarRes } from '../tasks/hsp/prepare-shared-har-res.js';
import { SdkVersion } from '../version/sdk-version.js';
import { ExtraParamsForHar, FileUtil, NativeComponents } from './file-util.js';
import { OhosLogger } from './log/ohos-logger.js';
import { AbstractProcessHarArtifacts } from '../tasks/abstract/abstract-process-har-artifacts.js';

/**
 * native cmake相关
 *
 * @since 2022/4/22
 */
export class CmakeUtil {
  private static _log: OhosLogger = OhosLogger.getLogger('Cmake');
  static readonly _nativeCacheDir: string = path.resolve(process.cwd(), '.hvigor', 'cache');
  static readonly ARCH_X86_64: string = 'x86_64';
  static readonly OHOS_MIN_NDK_FOR_X86_64: SdkVersion = new SdkVersion('3.2.10.6');
  static readonly HOS_MIN_NDK_FOR_X86_64: SdkVersion = new SdkVersion('3.2.2.6');

  static parseLibraries(output: string, target: string, abi: string) {
    const generator = new GenerateCodemodel(output);
    const codemodel: NativeCodemodel | undefined = generator.getCodemodel();
    const cmakeFiles: NativeCMakeFiles | undefined = generator.getObjFromIndex<NativeCMakeFiles>('cmakeFiles');
    const map: Map<string, NativeLibraryModel> = new Map<string, NativeLibraryModel>();
    codemodel?.configurations
      ?.flatMap((configuration) => configuration.targets)
      .forEach((targetModel) => {
        const library = GenerateNativeLibrary.parseLibrary(abi, targetModel, target, cmakeFiles);
        map.set(targetModel.name, library);
      });
    return map;
  }

  static checkNativeHeader(headerDir: string, moduleModel: ModuleModel, fileMap: Map<string, string>) {
    if (!fsExtra.existsSync(headerDir)) {
      CmakeUtil._log.printErrorExit('THE_PROPERTY_OF_HEADERPATH_SPECIFIES_DIRECTORY_%S_WHICH_DOES_NOT_EXIST', [headerDir, moduleModel.getProfilePath()]);
    }
    if (fsExtra.statSync(headerDir).isFile()) {
      CmakeUtil._log.printErrorExit('THE_PROPERTY_OF_HEADERPATH_SPECIFIES_DIRECTORY_%S_IS_NOT_DITECTORY',
        [headerDir, moduleModel.getProfilePath()],
        [[moduleModel.getProfilePath()]]
      );
    }
    const files = fsExtra.readdirSync(headerDir);
    files.forEach((fileName) => {
      if (fileMap.get(fileName)) {
        CmakeUtil._log.printErrorExit('THE_FOLLOWING_PATH_CONTAINS_A_FILE_WITH_THE_SAME_NAME', [fileName, headerDir, fileMap.get(fileName)]);
      }
      fileMap.set(fileName, headerDir);
    });
  }

  /**
   * 当native参数修改时, 删除cmake的缓存文件CMakeCache.txt, 使cmake重新编译.
   *
   * @param {string} workDir cache文件目录
   */
  static cleanCache(workDir: string): void {
    const cacheFile = path.resolve(workDir, 'CMakeCache.txt');
    FileUtil.deleteFile(cacheFile);
  }

  /**
   * 检查native配置CMakeLists.txt路径
   *
   * @param module CoreModuleModelImpl
   * @param targetName
   * @param nativeOpt
   */
  static getCmakeListDir(module: CoreModuleModelImpl, targetName: string, nativeOpt?: ExternalNativeOpt): string {
    const moduleDir = module.getProjectDir();
    if (!nativeOpt) {
      return path.resolve(moduleDir, './src/main/cpp');
    }
    let cmakeListsPath = nativeOpt.path;
    const profileFile: string = buildOptionPath.getTargetBuildOptPath(module, targetName, 'path');
    if (cmakeListsPath === undefined) {
      cmakeListsPath = './src/main/cpp/CMakeLists.txt';
    }
    if (cmakeListsPath.trim() === '') {
      this._log.printErrorExit('THE_CONFIGURED_CMAKE_SCRIPT_IN_EXTERNALNATIVEOPTIONS_PATH_CAN_NOT_BE_EMPTY', [profileFile]);
    }
    const cmakeFile: string = path.resolve(moduleDir, cmakeListsPath);
    if (!fsExtra.existsSync(cmakeFile)) {
      this._log.printErrorExit('THE_CONFIGURED_CMAKE_SCRIPT_IN_EXTERNALNATIVEOPTIONS_PATH_DOES_NOT_EXIST', [profileFile]);
    }
    if (path.basename(cmakeFile) !== 'CMakeLists.txt') {
      this._log.printErrorExit('THE_CONFIGURED_CMAKE_SCRIPT_NAME_%S_SHOULD_BE_CMAKELISTSTXT', [path.basename(cmakeFile), profileFile]);
    }
    return path.resolve(moduleDir, cmakeListsPath, '..');
  }

  /**
   * 根据项目类型及ndk版本添加X86_64架构
   *
   * @param abiFilters 已配置架构集合
   * @param isOhos isOhos
   * @param ndkVersion 当前编译ndk版本
   */
  static resolveArch(abiFilters: string[], isOhos: boolean, ndkVersion: SdkVersion): void {
    if (abiFilters.indexOf(this.ARCH_X86_64) > -1) {
      return;
    }
    const minNdkVersion: SdkVersion = isOhos ? this.OHOS_MIN_NDK_FOR_X86_64 : this.HOS_MIN_NDK_FOR_X86_64;
    if (ndkVersion.equals(minNdkVersion) || ndkVersion.isHigherThan(minNdkVersion)) {
      abiFilters.push(this.ARCH_X86_64);
    }
  }

  static getMayBeOccurErrorsFilePath(moduleModel: CoreModuleModelImpl) {
    return [moduleModel.getProfilePath(), " If there are no issues in this file, perhaps you have used 'overrides' in 'hvigorfile.ts', please check it."].join(
      `${os.EOL}\t`,
    );
  }

  /**
   * 检查native配置abiFilters
   * 如果未配置或空配置，则默认所有abiFilters
   *
   * @param {string[] | undefined} abiFilters abiFilters
   * @param isHarmonyOS isHarmonyOS
   * @param moduleModel CoreModuleModelImpl
   * @param targetName string
   * @return {string[]} abiFilters
   */
  static checkAbiFilters(abiFilters: string[] | undefined, isHarmonyOS: boolean, moduleModel: CoreModuleModelImpl, targetName: string): string[] {
    if (isHarmonyOS && abiFilters?.includes('armeabi-v7a')) {
      this._log.printErrorExit('ARMEABIV7A_NOT_SUPPORTED_FORHARMONYOS', [buildOptionPath.getTargetBuildOptPath(moduleModel, targetName, 'abiFilters')]);
    } else if (!isHarmonyOS && abiFilters?.length === 1 && abiFilters[0] === 'armeabi-v7a') {
      this._log.printErrorExit('ARMEABIV7A_NOT_SUPPORTED_OPENHARMONY_AS_THE_ONLY_OPTION',
        [buildOptionPath.getTargetBuildOptPath(moduleModel, targetName, 'abiFilters')]);
    }
    return abiFilters && abiFilters.length > 0 ? abiFilters : ['arm64-v8a'];
  }

  /**
   * native任务执行条件判断, 决定是否执行任务
   *
   * @param {ModuleModel} moduleModel ModuleModel
   * @param {ModuleTargetData} targetData ModuleTargetData
   * @param {ExternalNativeOpt} nativeOption ExternalNativeOpt
   * @return {boolean} 是否需要执行native任务
   */
  static nativeTaskCondition(moduleModel: ModuleModel, targetData: ModuleTargetData, nativeOption?: ExternalNativeOpt): boolean {
    if (nativeOption === undefined) {
      return false;
    }
    const targetName = targetData.getTargetName();
    let sourceSet = moduleModel.getSourceSetByTargetName(targetName);

    // har在构建target为ohosTest所生成的hap包时，需要包含target为default的c++
    if (targetName === DefaultTargetConst.OHOS_TEST_TARGET) {
      if (!moduleModel.isHarModule()) {
        return false;
      }
      sourceSet = moduleModel.getSourceSetByTargetName(DefaultTargetConst.DEFAULT_TARGET);
    }
    return sourceSet.getCodeMap().has(CodeType.CPP);
  }

  /**
   * 解析自定义参数并合入
   *
   * @param {string[]} commands 内置参数
   * @param {string} nativeArgs 自定义参数
   * @return {string[]} 合并后的cmake命令
   */
  static mergeCommandLine(commands: string[], nativeArgs: string[]): string[] {
    const cmakeArgs = CmakeUtil.parseCmakeArgs(nativeArgs, undefined);
    CmakeUtil.parseCmakeArgs(commands, cmakeArgs);
    return CmakeUtil.toCommandLine(cmakeArgs, commands);
  }

  /**
   * 将{CmakeArg}组合为命令行
   *
   * @param {Map<string, CmakeArg>} cmakeArgs 需要组合的命令
   * @param {string[]} original 原始命令行
   */
  static toCommandLine(cmakeArgs: Map<string, CmakeArg>, original: string[]): string[] {
    // 默认第一个参数为cmake执行程序
    const result: string[] = [original[0]];
    cmakeArgs.forEach((arg) => result.push(CmakeUtil.assembleArg(arg)));
    return result;
  }

  /**
   * 解析cmake命令行为{CmakeArg}
   *
   * @param {string[]} commands 需要解析的命令
   * @param {Map<string, CmakeArg> | undefined} container 参数容器
   * @return {Map<string, CmakeArg>} 解析完成的命令行
   */
  static parseCmakeArgs(commands: string[], container: Map<string, CmakeArg> | undefined): Map<string, CmakeArg> {
    container = container ? container : new Map<string, CmakeArg>();
    commands.forEach((command) => {
      const cmakeArg = CmakeUtil.parseArg(command.trim());
      if (cmakeArg !== undefined) {
        const key = cmakeArg.getPrefix() + cmakeArg.getKey();
        if (!container!.has(key)) {
          container!.set(key, cmakeArg);
        }
      }
    });
    return container;
  }

  static parseStlFromCMakeCache(replyFolder: string): CacheEntry {
    const defaultSharedStl: CacheEntry = { name: 'OHOS_STL', value: 'c++_shared', type: 'STRING' };
    const codemodel = new GenerateCodemodel(replyFolder);
    const cacheObj = codemodel.getObjFromIndex<CmakeCache>('cache');
    if (!cacheObj) {
      return defaultSharedStl;
    }
    return cacheObj.entries.find((item) => item.name === 'OHOS_STL') ?? defaultSharedStl;
  }

  static getCMakeArguments(args?: string[] | string): string[] {
    if (!args) {
      return [];
    }
    if (typeof args === 'string') {
      return [
        ...args
          .trim()
          .split(' ')
          .filter((arg) => arg.startsWith('-')),
      ];
    }
    return [...args].flatMap((arg) => arg.trim().split(' ')).filter((arg) => arg.startsWith('-'));
  }

  /**
   * 暂时仅支持
   * -{prefix}{key}
   * -{prefix}{key}={value}
   * 暂时默认参数前缀两个字符 like. -D
   *
   * @param {string} text 解析字符串
   * @return {CmakeArg | undefined} CmakeArg
   */
  static parseArg(text: string): CmakeArg | undefined {
    if (!text.startsWith('-')) {
      return undefined;
    }
    const prefix = text.substring(0, 2);
    const kv = text.substring(2);
    const equalIndex = kv.indexOf('=');
    if (equalIndex === -1) {
      return new CmakeArg(prefix, kv, undefined);
    } else {
      const key = kv.substring(0, equalIndex).trim();
      const value = kv.substring(equalIndex + 1).trim();
      return new CmakeArg(prefix, key, value);
    }
  }

  static getHarExtraParams(ohModuleTask: PrepareSharedHarRes | AbstractProcessHarArtifacts): ExtraParamsForHar {
    const extraParam: ExtraParamsForHar = {
      compatibleSdkVersion: ohModuleTask.targetData.getCompatibleApiVersion(),
      compatibleSdkType: ohModuleTask.targetData.isHarmonyOS() ? 'HarmonyOS' : 'OpenHarmony',
      obfuscated: ohModuleTask._harExtendInfo.isObfuscatedHar(),
      nativeComponents: [] as NativeComponents[],
    };
    if (!ohModuleTask.hasNativeOption) {
      delete extraParam.nativeComponents;
      return extraParam;
    }
    const librariesInfo = ohModuleTask.targetService.getBuildOption()?.nativeLib?.librariesInfo;
    const libraryMap = new Map<string, string[] | undefined>();
    // 获取build-profile.json5中的配置进行判断新增字段
    if (librariesInfo && librariesInfo.length > 0) {
      for (const library of librariesInfo) {
        libraryMap.set(library.name, library.linkLibraries);
      }
    }

    CmakeUtil.getCppSoName(ohModuleTask)?.forEach((soName) => {
      const soComponents: NativeComponents = {
        name: soName,
        compatibleSdkVersion: ohModuleTask.targetData.getCompatibleApiVersion(),
        compatibleSdkType: ohModuleTask.targetData.isHarmonyOS() ? 'HarmonyOS' : 'OpenHarmony',
        linkLibraries: libraryMap.get(soName) ?? [],
      };
      extraParam.nativeComponents?.push(soComponents);
    });
    return extraParam;
  }

  static getCppSoName(ohModuleTask: PrepareSharedHarRes | AbstractProcessHarArtifacts): Set<string> | undefined {
    if (ohModuleTask.hasNativeOption && ohModuleTask.cmakeListDir !== undefined) {
      const soNameSet: Set<string> = new Set<string>();
      const abiFilters = CmakeUtil.checkAbiFilters(
        ohModuleTask.targetService.getBuildOption().externalNativeOptions?.abiFilters,
        ohModuleTask.targetData.isHarmonyOS(),
        ohModuleTask.moduleModel,
        ohModuleTask.targetName,
      );
      for (const abiFilter of abiFilters) {
        const workDir = path.resolve(ohModuleTask.pathInfo.getNinjaWorkDir(), abiFilter);
        const map = CmakeUtil.parseLibraries(workDir, ohModuleTask.targetName, abiFilter);
        map.forEach((libraryModel) => {
          libraryModel.getNameOnDisk() && soNameSet.add(<string>libraryModel.getNameOnDisk());
        });
      }
      return soNameSet;
    }
    return undefined;
  }

  /**
   * 将CmakeArg组合为命令参数
   *
   * @param {CmakeArg} arg CmakeArg
   * @return {string} 命令
   */
  static assembleArg(arg: CmakeArg): string {
    if (arg.getValue() === undefined) {
      return arg.getPrefix() + arg.getKey();
    } else {
      return `${arg.getPrefix() + arg.getKey()}=${arg.getValue()}`;
    }
  }

  /**
   * 生成/.cxx/.cmake/api/v1/query/codemodel-v2文件夹, 用来请求cmake生成codemodel
   *
   * @param cxxDir .cxx工作目录
   */
  static mkCodeModelRequest(cxxDir: string): void {
    fsExtra.mkdirSync(path.resolve(cxxDir, '.cmake', 'api', 'v1', 'query', 'codemodel-v2'), { recursive: true });
    fsExtra.mkdirSync(path.resolve(cxxDir, '.cmake', 'api', 'v1', 'query', 'cmakeFiles-v1'), { recursive: true });
    fsExtra.mkdirSync(path.resolve(cxxDir, '.cmake', 'api', 'v1', 'query', 'cache-v2'), { recursive: true });
  }
}

/**
 * cmake参数model
 *
 */
export class CmakeArg {
  private _prefix: string;
  private _key: string;
  private _value: string | undefined;

  constructor(prefix: string, key: string, value: string | undefined) {
    this._prefix = prefix;
    this._key = key;
    this._value = value;
  }

  getPrefix(): string {
    return this._prefix;
  }

  setPrefix(value: string) {
    this._prefix = value;
  }

  getKey(): string {
    return this._key;
  }

  setKey(value: string) {
    this._key = value;
  }

  getValue(): string | undefined {
    return this._value;
  }

  setValue(value: string | undefined) {
    this._value = value;
  }
}
